<!-- Task发布任务图 -->
<template>
  <div ref="chartdev" class="wl-svg-tree">
    <!-- SVG图形 -->
    <svg ref="chartsvg" class="wl-svg-tree" />
  </div>
</template>

<script>
import * as d3 from 'd3'

export default {
  name: 'TaskChart',
  data() {
    return {
      construct: {},
      svg: '',
      gclass: ['wait', 'wait', 'wait', [], [], []], // finish, process, wait | [error]
      sequence: {
        id: 0,
        error: ''
      },
      hostSeq: []
    }
  },
  created() {
  },
  mounted() {
    this.svg = d3.select(this.$refs.chartsvg)
    this.$erd.listenTo(this.$refs.chartdev, (element) => {
      this.drawConstruct(element.offsetWidth, element.offsetHeight)
    })
  },
  beforeDestroyed() {
    this.$erd.removeAllListeners(this.$refs.chartdev)
  },
  methods: {
    setConstruct(data) {
      this.construct = data
      const element = this.$refs.chartdev
      this.drawConstruct(element.offsetWidth, element.offsetHeight)
    },
    startDo() {
      this.gclass = ['process', 'wait', 'wait', [], [], []]
      this.sequence = {
        id: 0,
        error: ''
      },
      this.hostSeq = []
      this.refeshChart()
    },
    addStepLog(data) { // 设置当前进度
      if (data === '') {
        return
      }
      if (data.sequence <= 3) {
        if (data.sequence > this.sequence.id || (data.status !== 0 && data.sequence >= this.sequence.id)) {
          this.sequence.id = data.sequence
          this.sequence.error = data.error
          switch (this.sequence.id) {
            case 0:
              this.gclass[0] = 'wait'
              this.gclass[1] = 'wait'
              this.gclass[2] = 'wait'
              break
            case 1:
              this.gclass[0] = 'process' + (data.status !== 0 ? ' error' : '')
              this.gclass[1] = 'wait'
              this.gclass[2] = 'wait'
              break
            case 2:
              this.gclass[0] = 'finish'
              this.gclass[1] = 'process' + (data.status !== 0 ? ' error' : '')
              this.gclass[2] = 'wait'
              break
            case 3:
              this.gclass[0] = 'finish'
              this.gclass[1] = 'finish'
              this.gclass[2] = 'process' + (data.status !== 0 ? ' error' : '')
              break
          }
          for (let i = 3; i <= 5; i++) {
            for (const j in this.gclass[i]) {
              this.gclass[i][j] = 'wait'
            }
          }
          this.refeshChart()
          return
        }
      } else {
        this.gclass[0] = 'finish'
        this.gclass[1] = 'finish'
        this.gclass[2] = 'finish'
        let idx = -1
        for (const i in this.construct.servers_info) {
          if (this.construct.servers_info[i].host === data.host) {
            idx = i
            break
          }
        }
        if (idx < 0) {
          return
        }
        if (!this.hostSeq[idx] || data.sequence > this.hostSeq[idx].id || (data.status !== 0 && data.sequence >= this.hostSeq[idx].id)) {
          this.hostSeq[idx] = {
            id: data.sequence,
            message: data.message,
            error: data.error
          }
          switch (this.hostSeq[idx].id) {
            case 4:
              this.gclass[3][idx] = 'process' + (data.status !== 0 ? ' error' : '')
              this.gclass[4][idx] = 'wait'
              this.gclass[5][idx] = 'wait'
              break
            case 5:
              this.gclass[3][idx] = 'finish'
              this.gclass[4][idx] = 'process' + (data.status !== 0 ? ' error' : '')
              this.gclass[5][idx] = 'wait'
              break
            case 6:
              this.gclass[3][idx] = 'finish'
              this.gclass[4][idx] = 'finish'
              this.gclass[5][idx] = 'process' + (data.status !== 0 ? ' error' : '')
              break
          }
          this.refeshChart()
        }
      }
    },
    doFinish(data, flag) { // 任务完成 flag=success,fail
      let idx = -1
      for (const i in this.construct.servers_info) {
        if (this.construct.servers_info[i].host === data.host) {
          idx = i
          break
        }
      }
      if (idx < 0 && !data.host) { // 整个任务结果
        if (flag === 'success' && !data.host) {
          this.gclass[0] = 'finish'
          this.gclass[1] = 'finish'
          this.gclass[2] = 'finish'
          for (let i = 3; i < 6; i++) {
            for (let j = 0; j < this.construct.servers_info.length; j++) {
              this.gclass[i][j] = 'finish'
            }
          }
        } else if (flag === 'fail') {
          // ...
        }
      } else if (idx >= 0 && data.host) { // 单服务器结果
        if (this.hostSeq[idx]) {
          this.hostSeq[idx].message = (flag === 'success') ? data.message : ''
          this.hostSeq[idx].error = (flag === 'fail') ? data.message : ''
          console.log('test', this.hostSeq[idx].id, flag)
          switch (this.hostSeq[idx].id) {
            case 4:
              this.gclass[3][idx] = 'finish' + (flag === 'fail' ? ' error' : '')
              this.gclass[4][idx] = 'wait'
              this.gclass[5][idx] = 'wait'
              break
            case 5:
              this.gclass[3][idx] = 'finish'
              this.gclass[4][idx] = 'finish' + (flag === 'fail' ? ' error' : '')
              this.gclass[5][idx] = 'wait'
              break
            case 6:
              this.gclass[3][idx] = 'finish'
              this.gclass[4][idx] = 'finish'
              this.gclass[5][idx] = 'finish' + (flag === 'fail' ? ' error' : '')
              break
          }
        }
      }
      // console.log(this.gclass, this.hostSeq)
      this.refeshChart()
    },
    doSuccess(data) {
      this.doFinish(data, 'success')
    },
    doFail(data) { // 任务失败
      this.doFinish(data, 'fail')
    },
    refeshChart() {
      const element = this.$refs.chartdev
      this.drawConstruct(element.offsetWidth, element.offsetHeight)
    },
    drawConstruct(width, height) { // 画图
      this.svg.selectAll('svg > *').remove()
      // 绘制Titiles
      const gTitles = this.svg.append('g')
      for (let i = 0; i < 6; i++) {
        let text = ''
        switch (i) {
          case 0: text = 'Deploy前置任务'; break
          case 1: text = 'Deploy'; break
          case 2: text = 'Deploy后置任务'; break
          case 3: text = 'Release前置任务'; break
          case 4: text = 'Release'; break
          case 5: text = 'Release后置任务'; break
        }
        gTitles.append('text').text(text).attr('x', Math.floor(width / 6.0 * i)).attr('y', 18).classed('title', true)
      }
      if (!this.construct.id) {
        return
      }
      const cnt = this.construct.servers_info.length
      this.svg.attr('height', 30 * (cnt + 1))
      const gTransform = this.svg.append('g')
      gTransform.attr('transform', 'translate(0,40)')
      // 绘制节点1，2，3
      const lineheight = 32; const circle_r = 12; const offset_circle_x = 13; const offset_circle_y = 0; const offset_text_x = 10; const offset_text_y = 4
      for (let i = 1; i <= 3; i++) {
        const x = Math.floor(width / 6.0 * (i - 1)) + offset_circle_x
        const y = Math.floor(cnt * lineheight / 2.0) - lineheight / 2 + offset_circle_y
        const gclass = this.gclass[i - 1]
        const g = gTransform.append('g').classed(gclass, true)
        // 画圆
        const circle = g.append('circle').attr('cx', x).attr('cy', y).attr('r', circle_r)
        if (gclass.indexOf('process') >= 0) {
          this.drawProcessCircle(g, x, y, 12, 3)
        }
        // 画线
        if (i >= 2 && i <= 3) {
          const x1 = Math.floor(width / 6.0 * (i - 2)) + circle_r * 2
          const x2 = Math.floor(width / 6.0 * (i - 1))
          const y1 = Math.floor(cnt * lineheight / 2.0) - lineheight / 2 + offset_circle_y
          g.append('line').attr('x1', x1 + 2).attr('y1', y1).attr('x2', x2).attr('y2', y1)
        }
        // 文本
        g.append('text').attr('x', x - 4).attr('y', y + 4).text(i)
      }
      // 绘制节点4，5，6
      for (let i = 4; i <= 6; i++) {
        for (let j = 0; j < cnt; j++) {
          const x = Math.floor(width / 6.0 * (i - 1)) + offset_circle_x
          const y = Math.floor(j * lineheight) + offset_circle_y
          let gclass = this.gclass[i - 1][j]
          if (!gclass) {
            gclass = 'wait'
          }
          const g = gTransform.append('g').classed(gclass, true)
          // 画圆
          const circle = g.append('circle').attr('cx', x).attr('cy', y).attr('r', circle_r)
          if (gclass.indexOf('process') >= 0) {
            this.drawProcessCircle(g, x, y, 12, 3)
          }
          // 画线
          if (i >= 4 && i <= 6) {
            const x1 = Math.floor(width / 6.0 * (i - 2)) + circle_r * 2
            const y1 = ((i === 4) ? Math.floor(cnt * lineheight / 2.0) - lineheight / 2 : Math.floor(j * lineheight)) + offset_circle_y
            const x2 = Math.floor(width / 6.0 * (i - 1))
            const y2 = Math.floor(j * lineheight)
            g.append('line').attr('x1', x1 + 2).attr('y1', y1).attr('x2', x2).attr('y2', y2)
          }
          // 文本
          g.append('text').attr('x', x - 4).attr('y', y + 4).text(i)
          if (i === 6) {
            g.append('text').attr('x', x + 24).attr('y', y + 4).text(this.construct.servers_info[j].name)
          }
        }
      }
    },
    drawProcessCircle(g, x, y, r, size) { // 画进行状态(旋转)
      const cl = g.append('circle').attr('cx', x - r).attr('cy', y + 2).attr('r', size)
        .attr('transform', 'rotate(186.022 ' + x + ' ' + y + ')')
        .attr('style', 'stroke:none; fill:rgb(48,49,51);')
      cl.append('animateTransform').attr('attributeName', 'transform')
        .attr('begin', '0s')
        .attr('dur', '3s')
        .attr('from', '0,' + x + ',' + y)
        .attr('to', '360,' + x + ',' + y)
        .attr('type', 'rotate')
        .attr('repeatCount', 'indefinite')
      return cl
    }
  }
}
</script>

<style>
  /* SVG 样式 */
  .wl-svg-tree {
    width: 100%;
    min-width: 800px;
  }
  .wl-svg-tree .title {
    fill: #409eff;
    font-size: 16px;
  }
  .wl-svg-tree text {
    display: block;
    white-space: nowrap;
    vertical-align: baseline;
    font-family: Helvetica Neue,Helvetica,Arial,sans-serif;
    font-size: 12px;
    color: #409eff!important;
  }
  .wl-svg-tree svg:not(:root) {
    overflow: hidden;
  }
  .wl-svg-tree :not(svg) {
    transform-origin: 0px 0px;
  }
  .wl-svg-tree .finish text {
    fill: #409eff;
  }
  .wl-svg-tree .finish circle {
    fill: #fff;
  }
  .wl-svg-tree .finish circle,
  .wl-svg-tree .finish line {
    stroke: #409eff;
    stroke-width: 2px;
  }
  .wl-svg-tree .wait circle,
  .wl-svg-tree .wait line {
    stroke: #c0c4cc;
    stroke-width: 2px;
  }
  .wl-svg-tree .wait circle {
    fill: #fff;
  }
  .wl-svg-tree .wait text {
    fill: #c0c4cc;
  }
  .wl-svg-tree .process circle {
    stroke: #303133;
    stroke-width: 2px;
    fill: #fff;
  }
  .wl-svg-tree .process line {
    stroke: #c0c4cc;
    stroke-width: 2px;
  }
  .wl-svg-tree .process text {
    fill: #303133;
  }
  .wl-svg-tree .error circle {
    stroke: red;
    stroke-width: 2px;
    fill: #fff;
  }
  .wl-svg-tree .error line {
    stroke: red;
    stroke-width: 2px;
  }
  .wl-svg-tree .error text {
    fill: red;
  }
</style>
